package com.bluedon.ism.commons.operationlog.aspect;

import com.bluedon.ism.commons.SystemContext;
import com.bluedon.ism.commons.entity.Account;
import com.bluedon.ism.commons.operationlog.LogValueStack;
import com.bluedon.ism.commons.operationlog.entity.Operatelog;
import com.bluedon.ism.commons.operationlog.exception.ClassTypeConfigException;
import com.bluedon.ism.commons.operationlog.process.Process;
import com.bluedon.ism.commons.operationlog.service.OperatelogService;
import com.bluedon.ism.commons.service.ConfigService;
import freemarker.cache.StringTemplateLoader;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.StringWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * 日志AOP拦截类
 */
@Aspect
public class OperateLogAspect {
	  private static final String DEFAULT_TEMPLATE_KEY = "_default_template_key";
	
	private static final Integer INIT_NUM = 1;
	
	private static final Log logger = LogFactory.getLog(OperateLogAspect.class);
	private static final Map<String, OperateLogAnnotation> cacheMap = Collections.synchronizedMap(new HashMap<String, OperateLogAnnotation>());
	private static final TemplateParserContext templateParserContext =new TemplateParserContext();

	private static final String OPERATELOGANNOTATION_PROCESSCLASS_CONFIG_ERROR = "OperateLogAnnotation的processClass配置不正确！";
	
	private ExpressionParser parser=new SpelExpressionParser();
	StandardEvaluationContext parserContext =new StandardEvaluationContext();
	
	@Resource
	private OperatelogService operatelogService;
	
	
	@Resource
	private ConfigService configService;
	
	/**
	 * 缓存中是否存在Key
	 */
	private boolean containsKey(String key) {
		return cacheMap.containsKey(key);
	}

	/**
	 * 获取cacheMap对象
	 * @param point
	 * @return
	 * @throws ClassNotFoundException
	 * @throws IllegalAccessException 
	 * @throws InstantiationException 
	 * @throws NoSuchMethodException 
	 * @throws InvocationTargetException 
	 * @throws IllegalArgumentException 
	 * @throws SecurityException 
	 */
	private OperateLogAnnotation getCacheMap(JoinPoint point) throws ClassNotFoundException  {

		String className = point.getTarget().getClass().getName();
		String methodName = point.getSignature().getName();
		String key = point.getSignature().toLongString();
		if (containsKey(key)) {
			logger.debug("key：[ "+ key +" ]");
			return cacheMap.get(key);
		} else {
			OperateLogAnnotation log = null ;
			if (!className.trim().isEmpty()) {
				Class clazz = Class.forName(className);
				Method[] methods = clazz.getMethods();
				for (Method method : methods) {
					if (methodName.equals(method.getName())) {
						if (method.isAnnotationPresent(OperateLogAnnotation.class)) {
							log = method.getAnnotation(OperateLogAnnotation.class);
							if(logger.isDebugEnabled()){
								logger.debug("className[" + className + "],methodName[" + methodName + "],moduleName[" + log.moduleName()
										+ "], operateTypeName[" + log.operateTypeName()
										+ "], message: [" + log.message() + "]");
							}
							cacheMap.put(key, log);
						}
						break;
					}
				}
			}
			return log;
		}
	}
	
	
	private Operatelog getOperateLog(JoinPoint point) throws ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException, NoSuchMethodException, InvocationTargetException, ClassTypeConfigException{
		OperateLogAnnotation log = getCacheMap(point);
		if(log==null)
			return null;
		Object oaccount = SystemContext.getContext().get(SystemContext.CURRENT_ACCOUNT);
		Object ohttpRequestMethod = SystemContext.getContext().get(SystemContext.HTTP_REQUEST_MOTHOD);
		Object ohttpRequest = SystemContext.getContext().get(SystemContext.HTTP_REQUEST);
		Operatelog oplog = new Operatelog();
		logger.debug("print oplog:" + oplog.toString());
		if(ohttpRequestMethod != null){
			String httpMethod = (String)ohttpRequestMethod;
			if(log.httpMethod().equalsIgnoreCase(httpMethod) || "ALL".equalsIgnoreCase(log.httpMethod()) ){
				parserContext.setRootObject(point);
				if(StringUtils.isBlank(log.moduleName()) && StringUtils.isBlank(log.operateTypeName())){
					 Class<?> processClass = log.processClass();
					 Object newInstance = processClass.newInstance();
					 if(newInstance instanceof Process){
						 Process process = (Process)newInstance;
						 HttpServletRequest request = (HttpServletRequest)ohttpRequest;
						 oplog = process.postProcess(request);
					 }else{
						 throw new ClassTypeConfigException(OPERATELOGANNOTATION_PROCESSCLASS_CONFIG_ERROR);
					 }
				}else{
					oplog.setModuleName(log.moduleName());
					oplog.setTypeName(log.operateTypeName());
				}
				oplog.setClassName(point.getTarget().getClass().getName());
				oplog.setMethodName(point.getSignature().getName());
				if(StringUtils.isNotBlank(log.message())){
					
					//处理message里面的模板信息
					LogValueStack logValueStack = LogValueStack.getContext();
					Map<String, Object> attr = logValueStack.getAttr();
					
					String ftlMessage = log.message();
					String processStringTemplate = processStringTemplate(ftlMessage, attr);
					logger.debug("processStringTemplate="+ processStringTemplate);
					oplog.setMessage(processStringTemplate);
				}
				oplog.setCreateTime(new Date());
				
				if(oaccount != null){
					Account account = ((Account)oaccount);
					oplog.setAccountId(account.getAid());
                    oplog.setIpAddress(account.getLoginIp());
				}else{
					return null;
				}
				
				return oplog;
			}
		}
		
		return null;
	}

	/**
	 * @annotation 定义拦截所有 方法上注释@OperateLog的方法
	 * @param point
	 * @throws Throwable
	 */

	@Transactional
	@Around("@annotation(com.bluedon.ism.commons.operationlog.aspect.OperateLogAnnotation)")
	public Object aroundIntercept(ProceedingJoinPoint point) throws Throwable {
		long starttime = System.currentTimeMillis();
		Object retValue = point.proceed();
		String _switch = configService.getValue("opt_log_switch");
		if("open".equalsIgnoreCase(_switch)){
			try {
				Operatelog oplog = getOperateLog(point);
				logger.debug("retValue:" + retValue);
				long endtime = System.currentTimeMillis();
				long time = endtime - starttime;
				if(oplog != null){
					logger.info(oplog.getClassName()+oplog.getMethodName()+"执行（ms）:" + time);
					oplog.setErrorMsg("耗时：" + time + "(ms)");
					operatelogService.saveLog(oplog);
				}
			} catch (Exception e) {
				logger.error(e.getMessage(),e);
				return retValue;
			}
		}
		return retValue;
	}

	@Transactional
	@AfterThrowing("@annotation(com.bluedon.ism.commons.operationlog.aspect.OperateLogAnnotation)")
	public void exceptionIntercept(JoinPoint point)
			throws ClassNotFoundException {
		String _switch = configService.getValue("opt_log_switch");
		if("open".equalsIgnoreCase(_switch)){
			try {
				Operatelog oplog = getOperateLog(point);
				if(oplog != null)
					operatelogService.saveLog(oplog);
			} catch (Exception e) {
				logger.error(e.getMessage(),e);
			}
		}
	}
	
	

	public void setOperatelogService(OperatelogService operatelogService) {
		this.operatelogService = operatelogService;
	}
	protected String processStringTemplate(String ftlmessage, Map<String, Object> map) {
        try {
        	Configuration cfg = new Configuration(); 
        	StringTemplateLoader loader = new StringTemplateLoader(); 
        	loader.putTemplate(DEFAULT_TEMPLATE_KEY, ftlmessage); 
        	cfg.setTemplateLoader(loader); 
        	cfg.setDefaultEncoding("UTF-8"); 
        	Template template = cfg.getTemplate(DEFAULT_TEMPLATE_KEY);  
        	StringWriter writer = new StringWriter();  
        	template.process(map, writer); 
        	return writer.toString();
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (TemplateException e) {
            throw new RuntimeException(e);
        }
    }

}
